/*
 
Copyright Eric C. McCreath 2012  <ericm@cs.anu.edu.au>

This file is part of the clapper-webcam-recorder project.

clapper-webcam-recorder is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.

clapper-webcam-recorder is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with clapper-webcam-recorder.  If not, see <http://www.gnu.org/licenses/>.

 */


 
#include <malloc.h>

#include <codec.h>
#include <ogg/ogg.h>
#include <theoraenc.h>
#include <stdlib.h>
#include <string.h>

#include <time.h>
#include <vorbis/vorbisenc.h>

#include "savevideo.h"
#include "util.h"
#include "sound.h"
#include "clapper.h"
#include "webcam.h"
#include "x11display.h"


struct savevideodata * setupogg(char * filename) {
    struct savevideodata *svd;
    svd = (struct savevideodata *) malloc(sizeof(struct savevideodata)); 
    if (!(svd->ogg_fp_va = fopen(filename, "wb"))) s1_exit("problem opening output file");

    return svd;
}

void setuptheora(struct savevideodata *svd, int x, int y, int framerate, int *ydatasize, int *udatasize, int *vdatasize) {
    if (ogg_stream_init(&(svd->ogg_os_video), 1)) s1_exit("problem creating ogg stream state");
svd->frame = 0;
    svd->xdims = x;
    svd->ydims = y;
    svd->chroma_format = TH_PF_420;
    th_info_init(&(svd->ti));    
    svd->ti.frame_width = ((svd->xdims + 15) >>4)<<4;
    svd->ti.frame_height = ((svd->ydims + 15)>>4)<<4;
    svd->ti.pic_width = svd->xdims;
    svd->ti.pic_height = svd->ydims;
    svd->ti.pic_x = 0;
    svd->ti.pic_y = 0;
    svd->ti.fps_numerator = framerate;
    svd->framerate = framerate;
    svd->ti.fps_denominator = 1;
    svd->ti.aspect_numerator = 1;
    svd->ti.aspect_denominator = 1;
    svd->ti.colorspace = TH_CS_UNSPECIFIED;
    svd->ti.pixel_fmt = TH_PF_420;
    svd->ti.target_bitrate = 0;
    svd->ti.quality = 60; // 0 to 63
    svd->ti.keyframe_granule_shift=3;

    svd->td=th_encode_alloc(&(svd->ti));  
    th_info_clear(&(svd->ti));
    
   

    th_comment_init(&(svd->tc));
    /* first packet will get its own page automatically */
    if(th_encode_flushheader(svd->td,&(svd->tc),&(svd->opv))<=0) s1_exit("Theora library error!!! - th_encode_flushheader");
    th_comment_clear(&(svd->tc));

    ogg_stream_packetin(&(svd->ogg_os_video),&(svd->opv));
    if(ogg_stream_pageout(&(svd->ogg_os_video),&(svd->ogv))!=1) s1_exit("Theora library error!!! - ogg_stream_pageout");
    fwrite(svd->ogv.header,1,svd->ogv.header_len,svd->ogg_fp_va);
    fwrite(svd->ogv.body,1,svd->ogv.body_len,svd->ogg_fp_va);

   
    svd->yuv_w = (svd->xdims + 15) & ~15;

    svd->yuv_h = (svd->ydims + 15) & ~15;

   
        (svd->ycbcr)[YPOS].width = svd->yuv_w;
        (svd->ycbcr)[YPOS].height = svd->yuv_h;
        (svd->ycbcr)[YPOS].stride = svd->yuv_w;
        (svd->ycbcr)[UPOS].width = (svd->chroma_format == TH_PF_444)? svd->yuv_w : (svd->yuv_w >> 1);
        (svd->ycbcr)[UPOS].stride = (svd->ycbcr)[UPOS].width;
        (svd->ycbcr)[UPOS].height = (svd->chroma_format == TH_PF_420)? (svd->yuv_h >> 1) : svd->yuv_h;
        (svd->ycbcr)[VPOS].width = (svd->ycbcr)[UPOS].width;
        (svd->ycbcr)[VPOS].stride = (svd->ycbcr)[UPOS].stride;
        (svd->ycbcr)[VPOS].height = (svd->ycbcr)[UPOS].height;

 
    
       *ydatasize = (svd->ycbcr)[YPOS].stride * (svd->ycbcr)[YPOS].height;
       *udatasize = (svd->ycbcr)[UPOS].stride * (svd->ycbcr)[UPOS].height;
       *vdatasize = (svd->ycbcr)[VPOS].stride * (svd->ycbcr)[VPOS].height;
}


void setuptheora2(struct savevideodata *svd) {
    int ret;    
    while ((ret = th_encode_flushheader(svd->td,&(svd->tc),&(svd->opv))) > 0){
      ogg_stream_packetin(&(svd->ogg_os_video),&(svd->opv));
    }
    if (ret < 0) s1_exit("Theora library error!!! - th_encode_flushheader");


    while ((ret = ogg_stream_flush(&(svd->ogg_os_video),&(svd->ogv))) > 0) {
        fwrite(svd->ogv.header,1,svd->ogv.header_len,svd->ogg_fp_va);
        fwrite(svd->ogv.body,1,svd->ogv.body_len,svd->ogg_fp_va);
    }
    if (ret < 0) s1_exit("Ogg library error!!! - ogg_stream_flush");
 
   }


void saveoggframe(struct savevideodata *svd, int last) {
   int iii;
    svd->frame++;
   

for (iii=0;iii<32;iii++)
  ((svd->ycbcr)[0].data)[iii] = ((svd->frame)>>iii & 1 ?  0x00 : 0xFF); 

int xmp, ymp;
/*
for (xmp = 0; xmp<10; xmp++)
for (ymp = 0; ymp<10; ymp++)
if (mousexpos + xmp < 640 && mouseypos + ymp < 480 && xmp < ymp)
     ((svd->ycbcr)[0].data)[(mouseypos + ymp) * 640 + (mousexpos + xmp)] = 0xFF;
*/

    if(th_encode_ycbcr_in(svd->td, svd->ycbcr)) s1_exit("problem encoding frame");
    
  //  if(!th_encode_packetout(svd->td, last, &(svd->op))) s1_exit("unable to read packets");
 
    while (th_encode_packetout(svd->td, last, &(svd->opv))) {

        ogg_stream_packetin(&(svd->ogg_os_video), &(svd->opv));

        if (last) { 
 //svd->ogg_os_video.e_o_s = 1;
            while(ogg_stream_flush(&(svd->ogg_os_video), &(svd->ogv))) {
                fwrite(svd->ogv.header, svd->ogv.header_len, 1, svd->ogg_fp_va);
                fwrite(svd->ogv.body, svd->ogv.body_len, 1, svd->ogg_fp_va);
            }
        } else {
            while(ogg_stream_pageout(&(svd->ogg_os_video), &(svd->ogv))) {
                fwrite(svd->ogv.header, svd->ogv.header_len, 1, svd->ogg_fp_va);
                fwrite(svd->ogv.body, svd->ogv.body_len, 1, svd->ogg_fp_va);
            }
        }
    }
}

void closetheora(struct savevideodata *svd) {
    th_encode_free(svd->td);

    int ret;
    while ((ret = ogg_stream_flush(&(svd->ogg_os_video),&(svd->ogv))) > 0) {
        fwrite(svd->ogv.header,1,svd->ogv.header_len,svd->ogg_fp_va);
        fwrite(svd->ogv.body,1,svd->ogv.body_len,svd->ogg_fp_va);
    }
    if (ret < 0) s1_exit("Ogg library error!!! - ogg_stream_flush in close");    
}

void closeogg(struct savevideodata *svd){
    fflush(svd->ogg_fp_va);
    fclose(svd->ogg_fp_va);
}


void setupvorbis(struct savevideodata *svd, long sr) {
    
    ogg_packet header;
    ogg_packet header_comm;
    ogg_packet header_code;

    svd->samplerate = sr;
    svd->sample = 0;

    if (svd->framerate == 0) s1_exit("the video needs to set up first with a frame rate");

    svd->samplesperframe = svd->samplerate / svd->framerate; 

    if ((svd->samplerate % svd->framerate) != 0 ) s1_exit("currently the sample rate needs to divide exactly by the framerate");


    vorbis_info_init(&(svd->vi));
    if(vorbis_encode_init_vbr(&(svd->vi),1,svd->samplerate,0.4)) s1_exit("problem with vorbis_encode_init_vbr");

    vorbis_comment_init(&(svd->vc));
    vorbis_comment_add_tag(&(svd->vc),"ENCODER","clapperWebCamRecorder");

    vorbis_analysis_init(&(svd->vd),&(svd->vi));
    vorbis_block_init(&(svd->vd),&(svd->vb));

    ogg_stream_init(&(svd->ogg_os_audio),2);

    vorbis_analysis_headerout(&(svd->vd),&(svd->vc),&header,&header_comm,&header_code);
    ogg_stream_packetin(&(svd->ogg_os_audio),&header); // automatically placed in its own page
    ogg_stream_packetin(&(svd->ogg_os_audio),&header_comm);
    ogg_stream_packetin(&(svd->ogg_os_audio),&header_code);

    int ret;
    while ((ret = ogg_stream_flush(&(svd->ogg_os_audio),&(svd->ogo))) > 0) {

        fwrite(svd->ogo.header,1,svd->ogo.header_len,svd->ogg_fp_va);
        fwrite(svd->ogo.body,1,svd->ogo.body_len,svd->ogg_fp_va);

    }
    if (ret < 0) s1_exit("Ogg library error!!! - ogg_stream_flush in vorbis init");    
}
  

#define min(A,B) (A<B?A:B)

long lastgranule = 0;
long lastgranulep = 0;

void saveoggvorbis(struct savevideodata *svd) {
    int pos, count;

    int firsttime = 1;
    pa_usec_t latency;
    double audiolat, fromstart, captime, droptime;
    double mustdrop;
  //  double frametime, audiotime; // in seconds from the timer start

    while(exitsequence < 3){
     
        count = READ;
        pulseread();
        latency = pulselatency();

        if (firsttime) {
            audiolat = (latency/1000000.0);
            fromstart = timefromstart();
            captime = getcapturetime();
            droptime = audiolat - (fromstart - captime);
            mustdrop = svd->samplerate * droptime;
 printf("audiolat: %.6f fromstart %.6f captime %.6f droptime %.6f mustdrop %.3f\n",audiolat,fromstart,captime,droptime,mustdrop);
           
            
            while (mustdrop > svd->samplesperframe) {
 printf("dropping\n");
                pulseread();
                latency = pulselatency();
                mustdrop -= count;
            }
            firsttime = 0;
        }
        float **buffer=vorbis_analysis_buffer(&(svd->vd),count);

        svd->sample += count;     

        int i;
        for(i=0;i<count;i++){
            buffer[0][i]=readbuffer[i]/32768.0f;
            pos++;
        }
      
       if (exitsequence == 1) {
           exitsequence = 2;
           vorbis_analysis_wrote(&(svd->vd),0);
       } else {
           vorbis_analysis_wrote(&(svd->vd),count);
       }
       
        while(vorbis_analysis_blockout(&(svd->vd),&(svd->vb))==1 ){
            vorbis_analysis(&(svd->vb),NULL);
            vorbis_bitrate_addblock(&(svd->vb));

            while(vorbis_bitrate_flushpacket(&(svd->vd),&(svd->opo))){

                ogg_stream_packetin(&(svd->ogg_os_audio),&(svd->opo));
                if (svd->opo.granulepos > svd->granulepause) {
                     saveframe(0); 
                     // frametime = saveframe(0); 
                    //  audiotime = timefromstart() - (latency/10000000.0);
//printf("ft %.3f at %.3f diff %.6f\n", frametime, audiotime, frametime-audiotime);
                }
                int ret;

              
             
               
                while ((ret = ogg_stream_flush(&(svd->ogg_os_audio),&(svd->ogo))) > 0) {
 
                    fwrite(svd->ogo.header,1,svd->ogo.header_len,svd->ogg_fp_va);
                    fwrite(svd->ogo.body,1,svd->ogo.body_len,svd->ogg_fp_va);
              
                }
               
                
           
             if (ret < 0) s1_exit("Ogg library error!!! - record in saveoggvorbis");    
       
         
             }
      }
      
      if (exitsequence == 2) {
         saveframe(1); 
         exitsequence = 3;
      }
   } 
   endProgram();
   
}

void closevorbis(struct savevideodata *svd) {
  ogg_stream_clear(&(svd->ogg_os_audio));
  vorbis_block_clear(&(svd->vb));
  vorbis_dsp_clear(&(svd->vd));
  vorbis_comment_clear(&(svd->vc));
  vorbis_info_clear(&(svd->vi));
}


